/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.fluent;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javax.xml.XMLConstants;

import org.sourceprojects.lycia.ConstraintValidator;
import org.sourceprojects.lycia.EndTagAware;
import org.sourceprojects.lycia.GenerationStrategy;
import org.sourceprojects.lycia.Generator;
import org.sourceprojects.lycia.GeneratorFactory;
import org.sourceprojects.lycia.LyciaConfigurator;
import org.sourceprojects.lycia.LyciaParser;
import org.sourceprojects.lycia.LyciaReaderType;
import org.sourceprojects.lycia.ProgressListener;
import org.sourceprojects.lycia.ResourceResolver;
import org.sourceprojects.lycia.TagDefinitionParser;
import org.sourceprojects.lycia.TypeConverter;
import org.sourceprojects.lycia.exceptions.LyciaConfigurationException;
import org.sourceprojects.lycia.exceptions.LyciaInitializationException;
import org.sourceprojects.lycia.internal.logging.LyciaLoggerWrapper;
import org.sourceprojects.lycia.internal.logging.LyciaLoggerWrapper.LyciaLoggerType;
import org.sourceprojects.lycia.internal.privileged.SetAccessible;
import org.sourceprojects.lycia.io.LyciaReader;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;

@SuppressWarnings("unchecked")
public abstract class FluentBuilder<B> {

	public static final <B> FluentBuilder<B> prepare() {
		return new FluentBuilderImpl<B>();
	}

	public static final ValidationBuilder validation(
			final Class<? extends ConstraintValidator<? extends Annotation>> validator) {

		return new ValidationBuilder(validator);
	}

	public static final NamespaceBuilder namespace(final String prefix,
			final String uri) {
		return new NamespaceBuilder(prefix, uri);
	}

	public static final MethodBuilder method(final Method method,
			final Object parser) {

		return new MethodBuilder() {
			{
				if (!method.isAccessible()) {
					SetAccessible.setAccessible(method);
				}

				if (Modifier.isStatic(method.getModifiers())) {
					throw new LyciaConfigurationException(
							"Lycia cannot handle static methods: "
									+ method.getName());
				}
			}

			@Override
			public Method getMethod() {
				return method;
			}

			@Override
			public Object getParser() {
				return parser;
			}
		};
	}

	public static final MethodBuilder method(final String methodname,
			final Object parser, final Class<?>... parameterTypes) {

		try {
			return new MethodBuilder() {
				private final Method method;

				{
					method = parser.getClass().getDeclaredMethod(methodname,
							parameterTypes);

					if (!method.isAccessible()) {
						SetAccessible.setAccessible(method);
					}

					if (Modifier.isStatic(method.getModifiers())) {
						throw new LyciaConfigurationException(
								"Lycia cannot handle static methods: "
										+ methodname);
					}
				}

				@Override
				public Object getParser() {
					return parser;
				}

				@Override
				public Method getMethod() {
					return method;
				}
			};
		} catch (final NoSuchMethodException e) {
			throw new LyciaInitializationException(e);
		}
	}

	public static final MethodBuilder method(final String methodname,
			final Class<?> parserClass, final Class<?>... parameterTypes) {

		try {
			return new MethodBuilder() {
				private final Method method;
				private final Object parser;

				{
					method = parserClass.getClass().getDeclaredMethod(
							methodname, parameterTypes);

					if (!method.isAccessible()) {
						SetAccessible.setAccessible(method);
					}

					if (Modifier.isStatic(method.getModifiers())) {
						throw new LyciaConfigurationException(
								"Lycia cannot handle static methods: "
										+ methodname);
					}

					parser = parserClass.newInstance();
				}

				@Override
				public Object getParser() {
					return parser;
				}

				@Override
				public Method getMethod() {
					return method;
				}
			};
		} catch (final NoSuchMethodException e) {
			throw new LyciaInitializationException(e);
		} catch (final InstantiationException e) {
			throw new LyciaInitializationException(e);
		} catch (final IllegalAccessException e) {
			throw new LyciaInitializationException(e);
		}
	}

	public static final NamespacesBuilder namespaces(
			final NamespaceBuilder... namespaceBuilders) {

		return new NamespacesBuilder(namespaceBuilders);
	}

	public static final ValidationsBuilder validations(
			final ValidationBuilder... validationBuilders) {

		return new ValidationsBuilder(validationBuilders);
	}

	public static final EventBuilder xpath(final String expression,
			final MethodBuilder methodBuilder) {

		return new XPathEventBuilder(expression, null, methodBuilder, null);
	}

	public static final EventBuilder regex(final String expression,
			final MethodBuilder methodBuilder) {

		return new RegexEventBuilder(expression, XMLConstants.NULL_NS_URI,
				methodBuilder, null);
	}

	public static final EventBuilder tag(final String expression,
			final MethodBuilder methodBuilder) {

		return new TagEventBuilder(expression, XMLConstants.NULL_NS_URI,
				methodBuilder, null);
	}

	public static final EventBuilder xpath(final String expression,
			final MethodBuilder methodBuilder,
			final NamespacesBuilder namespacesBuilder) {

		return new XPathEventBuilder(expression, namespacesBuilder,
				methodBuilder, null);
	}

	public static final EventBuilder regex(final String expression,
			final String namespaceUri, final MethodBuilder methodBuilder) {

		return new RegexEventBuilder(expression, namespaceUri, methodBuilder,
				null);
	}

	public static final EventBuilder tag(final String expression,
			final String namespaceUri, final MethodBuilder methodBuilder) {

		return new TagEventBuilder(expression, namespaceUri, methodBuilder,
				null);
	}

	public static final EventBuilder xpath(final String expression,
			final MethodBuilder methodBuilder,
			final ValidationsBuilder validationsBuilder) {

		return new XPathEventBuilder(expression, null, methodBuilder,
				validationsBuilder);
	}

	public static final EventBuilder regex(final String expression,
			final MethodBuilder methodBuilder,
			final ValidationsBuilder validationsBuilder) {

		return new RegexEventBuilder(expression, XMLConstants.NULL_NS_URI,
				methodBuilder, validationsBuilder);
	}

	public static final EventBuilder tag(final String expression,
			final MethodBuilder methodBuilder,
			final ValidationsBuilder validationsBuilder) {

		return new TagEventBuilder(expression, XMLConstants.NULL_NS_URI,
				methodBuilder, validationsBuilder);
	}

	public static final EventBuilder xpath(final String expression,
			final MethodBuilder methodBuilder,
			final NamespacesBuilder namespacesBuilder,
			final ValidationsBuilder validationsBuilder) {

		return new XPathEventBuilder(expression, namespacesBuilder,
				methodBuilder, validationsBuilder);
	}

	public static final EventBuilder regex(final String expression,
			final String namespaceUri, final MethodBuilder methodBuilder,
			final ValidationsBuilder validationsBuilder) {

		return new RegexEventBuilder(expression, namespaceUri, methodBuilder,
				validationsBuilder);
	}

	public static final EventBuilder tag(final String expression,
			final String namespaceUri, final MethodBuilder methodBuilder,
			final ValidationsBuilder validationsBuilder) {

		return new TagEventBuilder(expression, namespaceUri, methodBuilder,
				validationsBuilder);
	}

	public static final <B> EventBuilder parser(
			final TagDefinitionParser<B> tagDefinitionParser) {

		return new TagDefinitionParserEventBuilder<B>(tagDefinitionParser);
	}

	public static final <B> EventBuilder parser(
			final Class<? extends TagDefinitionParser<B>> tagDefinitionParserClass) {

		return new TagDefinitionParserEventBuilder<B>(tagDefinitionParserClass);
	}

	public static final <B> EventBuilder pojoParser(final Object tagEventParser) {

		return new AnnotatedPojoEventBuilder<B>(tagEventParser);
	}

	public static final <B> AnnotatedPojoEventBuilder pojoParser(
			final Class<?> pojoTagParserClass) {

		return new AnnotatedPojoEventBuilder<B>(pojoTagParserClass);
	}

	public static final <B> ConfigurationBuilder endTagListener(
			final EndTagAware<B> listener) {

		return ConfigurationBuilderHelper.createEndTagReachedListener(listener);
	}

	public static final ConfigurationBuilder validateSchema(
			final boolean validatingSchema) {

		return ConfigurationBuilderHelper
				.createValidatingSchema(validatingSchema);
	}

	public static final ConfigurationBuilder generationStrategy(
			final GenerationStrategy generationStrategy) {

		return ConfigurationBuilderHelper.createGenerationStrategy(
				generationStrategy, null);
	}

	public static final ConfigurationBuilder generationStrategy(
			final Class<? extends Generator> generatorClass) {

		return ConfigurationBuilderHelper.createGenerationStrategy(
				GenerationStrategy.Customized, generatorClass);

	}

	public static final ConfigurationBuilder contentHandler(
			final ContentHandler... contentHandlers) {

		return ConfigurationBuilderHelper.createContentHandler(contentHandlers);
	}

	public static final ConfigurationBuilder dataConverter(
			final TypeConverter... dataConverters) {

		return ConfigurationBuilderHelper.createDataConverter(dataConverters);
	}

	public static final ConfigurationBuilder configurator(
			final Class<? extends LyciaConfigurator> configuratorClass) {

		return ConfigurationBuilderHelper
				.createXmlParserConfigurator(configuratorClass);
	}

	public static final ConfigurationBuilder logBy(
			final LyciaLoggerType loggerType) {

		LyciaLoggerWrapper.setLyciaLoggerType(loggerType);
		return ConfigurationBuilderHelper.createDoNothingConfigurator();
	}

	public static final <B> ConfigurationBuilder contextObject(
			final Class<B> contextObjectClass) {

		return ConfigurationBuilderHelper
				.createContextObjectConfigurator(contextObjectClass);
	}

	public static final <B> ConfigurationBuilder contextObject(
			final B contextObject) {
		return ConfigurationBuilderHelper
				.createContextObjectConfigurator(contextObject);
	}

	public static final <B> ConfigurationBuilder nullAware(
			final boolean nullAware) {
		return ConfigurationBuilderHelper
				.createNullAwareConfigurator(nullAware);
	}

	public static final <B> ConfigurationBuilder resourceResolver(
			final ResourceResolver resourceResolver) {

		return ConfigurationBuilderHelper
				.createResourceResolverConfigurator(resourceResolver);
	}

	public static final <B> ConfigurationBuilder errorHandler(
			final ErrorHandler errorHandler) {

		return ConfigurationBuilderHelper
				.createErrorHandlerConfigurator(errorHandler);
	}

	public static final <B> ConfigurationBuilder progressListener(
			final ProgressListener... progressListeners) {

		return ConfigurationBuilderHelper
				.createProgressListenerConfigurator(progressListeners);
	}

	public static final <B> ConfigurationBuilder reader(
			final Class<? extends LyciaReader> lyciaReaderClass) {

		return ConfigurationBuilderHelper
				.createLyciaReaderConfigurator(lyciaReaderClass);
	}

	public static final <B> ConfigurationBuilder reader(
			final LyciaReaderType readerType) {

		return ConfigurationBuilderHelper
				.createLyciaReaderConfigurator(readerType);
	}

	public static final <B> ConfigurationBuilder cacheExpirationTime(
			final long millis) {
		return ConfigurationBuilderHelper
				.createCacheExpirationTimeConfigurator(millis);
	}

	public static final <B> ConfigurationBuilder generatorFactory(
			final GeneratorFactory<B> generatorFactory) {

		return ConfigurationBuilderHelper
				.createGeneratorFactoryConfigurator(generatorFactory);
	}

	public static final <B> ConfigurationBuilder ignoreComments(
			final boolean ignore) {

		return ConfigurationBuilderHelper
				.createIgnoreCommentsConfigurator(ignore);
	}

	public abstract LyciaParser<B> build();

	public abstract FluentBuilder<B> parser(EventBuilder... eventBuilders);

	public abstract FluentBuilder<B> transformer(EventBuilder... eventBuilders);

	public abstract FluentBuilder<B> configure(
			ConfigurationBuilder... configurationBuilders);

	public abstract FluentBuilder<B> makeAndroidCompatible();

	public abstract FluentBuilder<B> makeThreadsafe();

	public abstract FluentBuilder<B> useClassLoader(ClassLoader classLoader);

}
